/*
 *  Copyright 2013 Utkin Dmitry
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

#ifndef PROTOQTRPC_SOCKETRPCCHANNEL_H
#define PROTOQTRPC_SOCKETRPCCHANNEL_H

#include <QThread>
#include <QAbstractSocket>
#include <google/protobuf/service.h>

class QHostAddress;

namespace protoqtrpc
{

class RpcMessage;
class SocketRpcChannelPrivate;

/**
 * @brief Socket implementation of RPC Channel
 * An SocketRpcChannel represents a communication line to a Service
 * which can be used to call that Service's methods.
 * The Service may be running on another machine.
 */
class SocketRpcChannel: public QThread, public ::google::protobuf::RpcChannel
{
    Q_OBJECT

public:
    /**
     * @brief Constructs SocketRpcChannel.
     * Constructs empty SocketRpcChannel to connect to a service
     */
    SocketRpcChannel();

    /**
     * @brief Constructs SocketRpcChannel.
     * Constructs SocketRpcChannel to connect to a service running on
     * specified host and port
     * @param hostPort Server host and port in form "host:port"
     */
    SocketRpcChannel(const QString& hostPort);

    /**
     * @brief Constructs SocketRpcChannel.
     * Constructs SocketRpcChannel to connect to a service running on
     * specified host and port
     * @param host Server host
     * @param port Servier port
     */
    SocketRpcChannel(const QHostAddress& host, quint16 port);

    /**
     * @brief Destroys the channel
     */
    virtual ~SocketRpcChannel();

    /**
     * @brief Connect to service
     * Connects a empty SocketRpcChannel to a service running on
     * specified host and port
     * @param host Server host
     * @param port Servier port
     */
    bool connectToService(const QHostAddress& host, quint16 port);


    /**
     * @brief Get the host of service
     * @return Host
     */
    const QHostAddress& host() const;

    /**
     * @brief Get the port of service
     * @return Port
     */
    quint16 port() const;

    /**
     * @brief Tests is channel connected to a service or not
     * @return true, if channel is connected to a service
     */
    bool isConnectedToService() const;

    /**
     * @brief Sets timeout for all requests
     * @param timeout Timeout in milliseconds, 0 - infinite timeout
     */
    void setTimeout(quint64 timeout = 30000);

    /**
     * @brief Gets timeout
     * @return timeout in milliseconds
     */
    quint64 timeout() const;

protected slots:
    /**
     * @brief Call the given method of the remote service.
     * @param method Method to call
     * @param controller RPC controller
     * @param request Request to service
     * @param response Received response
     * @param done Closure that will be runned when request is finished
     */
    virtual void CallMethod(const ::google::protobuf::MethodDescriptor* method,
            ::google::protobuf::RpcController* controller,
            const ::google::protobuf::Message* request,
            ::google::protobuf::Message* response,
            ::google::protobuf::Closure* done);

    /**
     * @brief Received message from server
     * @param msg Message that was received
     * @param peerAddress Client IP
     * @param peerPort Client port
     */
    void messageReceived(const protoqtrpc::RpcMessage& msg);

    /**
     * @brief An socket error ocurred
     */
    void socketError(QAbstractSocket::SocketError err);

    /**
     * @brief Checks for expired callbacks
     */
    void expire();

    /**
     * @brief Starts message cancelling
     * @param rpcId message id
     */
    void cancel(quint64 rpcId);

private:
    struct ActiveRequest;
    bool handleMessage(const RpcMessage* msg, ActiveRequest* req);
    void updateTimer();
    void run();

private:
    Q_DECLARE_PRIVATE(SocketRpcChannel)
    Q_DISABLE_COPY(SocketRpcChannel)
    SocketRpcChannelPrivate* d_ptr;
};

}

#endif
